AWS SES와 Lambda를 활용해서 나만의 Domain 주소를 Gmail 주소로 사용하기
2025.01.31
기독교 선교단체 ECU(Evangelical Christian Union, 복음주의 학생연합)의 홈페이지는
https://ecukorea.com
에서 운영되고 있다. 사역에 사용되는 정보를 모아두는 Google 계정의 이메일은 ecu.official.adm@gmail.com 인데, 길이가 길다보니 아무래도 한번 보고 기억하기는 어렵다는 단점이 있다. 직접 SMTP 서버를 제작하는 수고를 들이지 않고, 원하는 도메인에 해당하는 메일 주소를(admin@ecukorea.com) Gmail에서 사용하게 되는 과정을 소개한다.
ECU 홈페이지의 서버는 EC2 인스턴스에 띄워져있는 구조이고, Route53 서비스를 사용하고 있던터라 AWS의 서비스를 활용하는게 편리할 것으로 예상되었다. 원래는 사용자들에게 마케팅 등의 목적으로 이메일을 대량 전송할 때 사용하라고 만들어진 SES 이지만, 여러 글들을 참고하며 이메일 포워딩 용으로 사용해보기로 했다.
AWS SES (Simple Email Service)
원하는 Region에서 SES service가 지원되는지 확인 하고(예전에는 서울 Region에서 지원이 안됐던 것 같은데, 현재는 지원된다), 해당 region에서 SES setup을 진행한다. 나는 서울 region에다가 setup을 진행했다.
Get set up verification email은 기존 gmail 주소를 사용하면 되고, sending domain은 별도의 subdomain을 사용할게 아니라면 그냥 domain name을 입력하면 된다.
Verify sending domain domain 인증을 할때는, DKIM 설정을 enable 해주도록 하자. 그렇게 생성된 DNS record들(CNAME)은 바로 Route53에 추가한다.
DMARC TXT record를 Route53에 추가 (참고)
MX record를 Route53에 추가 (직접 해야함) 2,3번에서는 route53에 바로 추가할 수 있도록 버튼이 있지만, MX record는 직접 입력해야한다. 값은
10 inbound-smtp.{{region}}.amazonaws.com
로 하면 된다.Request production access 기본적으로 sandbox 모드이기 때문에, verified address 가 아닌 주소로는 메일 발송이 불가능하다. production access를 요청하는 순간 왜 해당 기능이 필요한지, 어떻게 사용할 것인지에 대한 상세한 설명을 요청하는 메일이 발송된다. 이 때, 해당 메일에 답변을 하는게 아닌,
Support Center
쪽에 생성된 Case에 답변을 작성하면 된다. 나의 경우에는 하루정도 걸려서 production access가 승인되었다.SMTP credential 생성 Gmail에서
admin@ecukorea.com
으로 메일을 발송하기 위해서 SMTP 계정이 필요하다. 결국에는 AWS IAM 계정을 만드는 것이고, 해당 계정을 추후 Gmail 쪽에 입력할 것이다.
S3
해당 계정으로 수신된 메일이 저장되는 Bucket을 생성해야한다. 생성시 public access는 전부 차단하면 되고, 해당 bucket의 policy는 다음과 같이 작성하면 된다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSESToWriteEmail",
"Effect": "Allow",
"Principal": {
"Service": "ses.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::{{Bucket Name}}/*",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "{{AWS Account ID}}"
},
"ArnLike": {
"aws:SourceArn": "arn:aws:ses:{{region}}:{{AWS Account ID}}:receipt-rule-set/*"
}
}
}
]
}
메일을 계속 쌓아 놓을 목적이 아니라면 Lifecyle rule 생성을 추천한다.
rule scope는 bucket 내의 모든 object를 대상으로 하거나, 해당 버킷 내에 폴더를 생성했다면 해당 폴더로 scope를 제한해도 좋을 것 같다.
이 때 action에는 Permanently delete noncurrent versions of objects
를 선택하고, Days after objects become noncurrent
에 원하는 날 수를 입력하면, 해당 날만큼만 S3에 존재하다가 파일은 삭제될 것이다.
Lambda
Lambda function 생성시 Node.js verison 22를 사용했고, 코드를 봤을 때, arm64
아키텍쳐에서도 무리없이 동작할 것 같아서 x86_64
가 아닌 arm64
아키텍쳐를 선택했다.
index.js
코드는 이 파일 내용을 그대로 사용하고, defaultConfig
값만 적절한 값으로 입력해주면 된다.
var defaultConfig = {
// ex) admin@ecukorea.com
fromEmail: "{{forwarding 할 때 사용할 이메일}}",
subjectPrefix: "",
emailBucket: "{{이메일 저장되는 S3 버킷 이름}}",
// 버킷내에 별도의 폴더를 사용하지는 않아서 빈 값으로 놔둠.
emailKeyPrefix: "",
allowPlusSign: true,
forwardMapping: {
// ex) admin@ecukorea.com
"{{forwarding 할 때 사용할 이메일}}": [
// ex) ecu.official.adm@gmail.com
"{{gmail 계정 이메일 주소}}",
]
}
};
// ...
Lambda 함수의 Permission을 설정해주는 방법은 다음과 같다.
- Configuration - Permissions 에서 보이는 role을 클릭
- Add permissions - Create inline policy - JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": "ses:SendRawEmail",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::{{Bucket Name}}/*"
}
]
}
Cloudwatch
위에서 기존에 생성된 role의 policy를 보면, AWSLambdaBasicExecutionRole
이 있을 것이다. 이 때 Resource 쪽에 적혀있는 log group이 생성되어있는지 확인하기 바란다.
AWS Lambda 실행 로그는 Cloudwatch 쪽에 기록되려 할텐데, 해당 로그 그룹이 생성되지 않았다면 오류가 발생한다. 나의 경우에는 /aws/lambda/{lambda function name}
으로 로그 그룹을 생성해줬고, 로그가 잘 들어왔다.
Gmail 계정 추가
지금까지의 과정은 이메일이 수신될 때와 관련한 작업이다. 즉, 내가 원하는 도메인 주소로 상대방이 이메일을 발송할 수 있도록 한 것으로 이해하면 된다. 상대방이 나에게 보낸것처럼, 나도 상대방에게 이메일을 보내려면 Gmail에 AWS SMTP 정보를 입력해주면 된다. 처음에 SES setup 단계에서 생성한 SMTP Credentials를 사용할 때다.
해당 정보로 메일 발송이 잘 되는지 확인해보려면 swaks
를 사용해보는 것도 추천한다.
brew install swaks
swaks --to {{destination@email.address}} --server email-smtp.{{region}}.amazonaws.com --port {{25 or 587 or 2587}} --auth-user {{Account ID}} --auth-password {{Account PASSWORD}} --tls --from {{verified@email.address}}
Gmail에서 우측 상단의 톱니바퀴 - 모든 설정 보기 - 계정 및 가져오기 - 다른 주소에서 메일 보내기 - 다른 이메일 주소 추가
이메일 주소는 우리가 원하는 도메인의 이메일 주소를 입력하고, SMTP 서버는 Amazon SES의 SMTP settings 탭에 들어가면 보이는 SMTP endpoint를 입력해준다. 사용자 이름 / 비밀번호에는 SMTP Credential에 있는 값을 입력해주고 계정 추가해주면 완료된다!
최종 아키텍쳐
1. 이메일 수신
2. 이메일 발신
문제점
Lambda 코드를 읽어보면 확인할 수 있겠지만, defaultConfig.fromEmail
값이 어떻게 사용되는지 주목해 볼 필요가 있다.
// SES does not allow sending messages from an unverified address,
// so replace the message's "From:" header with the original
// recipient (which is a verified domain)
header = header.replace(
/^from:[\t ]?(.*(?:\r?\n\s+.*)*)/mgi,
function(match, from) {
var fromText;
if (data.config.fromEmail) {
fromText = 'From: ' + from.replace(/<(.*)>/, '').trim() +
' <' + data.config.fromEmail + '>';
} else {
fromText = 'From: ' + from.replace('<', 'at ').replace('>', '') +
' <' + data.originalRecipient + '>';
}
return fromText;
});
SES에서는 verified address만 발송자로 사용할 수 있기 때문에, 다른 사람의 이메일 주소도 나의 이메일 주소로 변경시켜버리는 것이다. Gmail에서는 답장에 해당하는 이메일 주소가 별도로 관리되어서 문제 없지만, 네이버 메일의 경우에는 답장을 할 수 없는 구조가 되어버리고 말 것이다.(25년 1월 기준)
정리
우여곡절 끝에 admin@ecukorea.com 을 gmail에서 사용할 수 있게 되었다. 세팅 해줘야할 것은 생각보다 많이는 없지만(?), MX 레코드를 추가해주지 않아서 왜 S3로 메일이 저장되지 않는지 삽질을 하는 시간이 길었다. 아마도 이 글을 따라하는 분이 있다면 production access 요청에서 막혀있을 것이라고 생각이 된다. 테스트 하는 과정에서는 메일 발송/수신에 사용할 주소를 verified address에 추가하면 가능하니 참고하기 바란다.